﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;

namespace System.Windows.Media.DirectShow.Filters
{
    public abstract class BaseInputPin : BasePin, IMemInputPin
    {
        private bool _isReadOnly;

        public BaseInputPin(IBaseFilter filter)
            : base(filter)
        {

        }

        public override PinDirection Direction
        {
            get { return PinDirection.Input; }
        }

        #region IMemInputPin Members

        public abstract bool ReceiveCanBlock
        {
            get;
        }

        void IMemInputPin.Receive(IMediaSample sample)
        {
            ReceiveMediaSample(sample);
        }

        IMemAllocator IMemInputPin.GetAllocator()
        {
            return GetAllocator();
        }

        void IMemInputPin.NotifyAllocator(IMemAllocator allocator, bool isReadOnly)
        {
            NotifyAllocator(allocator, isReadOnly);
        }

        AllocatorProperties IMemInputPin.GetAllocatorRequirements()
        {
            return GetAllocatorRequirements();
        }
        #endregion

        protected abstract void Receive(IMediaSample sample);

        protected override void Disconnect()
        {
            if (this.Filter.State != FilterState.Stopped)
                throw new InvalidOperationException("Cannot disconnect while filter is running");

            IAsyncReader reader = this.ConnectedPin as IAsyncReader;
            if (reader != null)
                reader.RequestCompleted -= new EventHandler<ReaderAsyncCompletedEventArgs>(reader_RequestCompleted);

            this.ConnectedPin = null;
            this.ConnectedMediaType = null;
        }

        protected override void Connect(IPin receivePin, AMMediaType mediaType)
        {
            throw new InvalidOperationException("Cannot call this method. This is an input pin");
        }

        protected bool IsPullMode
        {
            get { return (this.ConnectedPin is IAsyncReader); }
        }

        protected override void ReceiveConnection(IPin connector, AMMediaType mediaType)
        {
            if (this.ConnectedPin != null)
                throw new InvalidOperationException("Pin already connected");

            this.ConnectedPin = connector;
            this.ConnectedMediaType = mediaType ?? this.ConnectedPin.MediaTypes.First();

            IAsyncReader reader = connector as IAsyncReader;
            if (reader != null)
            {
                this.Allocator = reader.RequestAllocator(GetAllocator(), GetAllocatorRequirements());
                reader.RequestCompleted += new EventHandler<ReaderAsyncCompletedEventArgs>(reader_RequestCompleted);
            }
        }

        protected void RequestSample(long startMediaTime, long endMediaTime, object state)
        {
            IAsyncReader reader = this.ConnectedPin as IAsyncReader;
            if (reader != null)
                reader.Request(startMediaTime, endMediaTime, state);
            else
                throw new InvalidOperationException("Connected pin does not support pull mode");
        }

        private void ReceiveMediaSample(IMediaSample sample)
        {
            sample.AddRef();

            this.QueueWork(s =>
            {
                try
                {
                    if (this.ConnectedPin == null)
                        throw new InvalidOperationException("Pin is not connected");
                    if (this.Filter.State == FilterState.Stopped)
                        return;

                    // Trust the media type
                    // Output pin should call QueryAccect before
                    this.ConnectedMediaType = sample.MediaType;

                    Receive(sample);
                }
                finally
                {
                    sample.Release();
                }
            });
        }

        protected virtual void RequestNextSample(IMediaSample sample)
        {
            this.RequestSample(sample.EndMediaTime, sample.EndMediaTime + this.Allocator.Properties.BufferSize, null);
        }

        protected virtual IMemAllocator GetAllocator()
        {
            return null;
        }

        protected virtual void NotifyAllocator(IMemAllocator allocator, bool isReadOnly)
        {
            this.Allocator = allocator;
            _isReadOnly = isReadOnly;
        }

        protected virtual AllocatorProperties GetAllocatorRequirements()
        {
            return new AllocatorProperties() { BuffersCount = 1, BufferSize = 0x1000 };
        }

        private void reader_RequestCompleted(object sender, ReaderAsyncCompletedEventArgs e)
        {
            if (e.Error == null)
                ReceiveMediaSample(e.Sample);
        }

        public override void Run(TimeSpan start)
        {
            base.Run(start);

            if (this.IsPullMode)
            {
                this.Allocator.Commit();
                RequestSample(0, this.Allocator.Properties.BufferSize, null);
            }
        }

        public override void Inactive()
        {
            base.Inactive();

            if (this.IsPullMode)
                this.Allocator.Decommit();
        }
    }
}
